/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/

//TODO: windows.h pulls in winsock.h but enetneeds winsock2.h so I have removed winsock.h
//include from windows.h, this should be fixed so that source code is compatible with all setups
//Actually it would be better to use a standalone lib for input
#include "win_local.h"
#include "../client/client.h"



static byte		win_scanCodeToKey[128] = {
	0			, K_ESCAPE		, '1'			, '2'			,
	'3'			, '4'			, '5'			, '6'			,
	'7'			, '8'			, '9'			, '0'			,
	'-'			, '='			, K_BACKSPACE	, K_TAB			,
	'q'			, 'w'			, 'e'			, 'r'			,
	't'			, 'y'			, 'u'			, 'i'			,
	'o'			, 'p'			, '['			, ']'			,
	K_ENTER		, K_LCTRL		, 'a'			, 's'			,
	'd'			, 'f'			, 'g'			, 'h'			,
	'j'			, 'k'			, 'l'			, ';'			,
	'\''		, '`'			, K_LSHIFT		, '\\'			,
	'z'			, 'x'			, 'c'			, 'v'			,
	'b'			, 'n'			, 'm'			, ','			,
	'.'			, '/'			, K_RSHIFT		, K_KP_STAR		,
	K_LALT		, K_SPACE		, K_CAPSLOCK	, K_F1			,
	K_F2		, K_F3			, K_F4			, K_F5			,
	K_F6		, K_F7			, K_F8			, K_F9			,
	K_F10		, K_PAUSE		, K_SCROLLLOCK	, K_HOME		,
	K_UPARROW	, K_PGUP		, K_KP_MINUS	, K_LEFTARROW	,
	K_KP_5		, K_RIGHTARROW	, K_KP_PLUS		, K_END			,
	K_DOWNARROW	, K_PGDN		, K_INS			, K_DEL			,
	0			, 0				, 0				, K_F11			,
	K_F12		, 0				, 0				, 0				,
	0			, K_APPS		, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0				,
	0			, 0				, 0				, 0
}; 


/*
 ==================
 WIN_LowLevelKeyboardProc
 ==================
*/
//#define _WIN32_WINNT		0x0501
//#define WINVER				0x0501
//#undef _WINUSER_
//#include <winuser.h>
static LRESULT CALLBACK WIN_LowLevelKeyboardProc (int nCode, WPARAM wParam, LPARAM lParam){

	KBDLLHOOKSTRUCT	*kb = (KBDLLHOOKSTRUCT *)lParam;

	if (nCode < 0 || nCode != HC_ACTION)
		return CallNextHookEx(sys.hHookKeyboard, nCode, wParam, lParam);

	switch (wParam){
	case WM_KEYDOWN:
		switch (kb->vkCode){
		case VK_LWIN:
			Key_Event(K_LWIN, true, kb->time);
			return 1;
		case VK_RWIN:
			Key_Event(K_RWIN, true, kb->time);
			return 1;
		}

		break;
	case WM_KEYUP:
		switch (kb->vkCode){
		case VK_LWIN:
			Key_Event(K_LWIN, false, kb->time);
			return 1;
		case VK_RWIN:
			Key_Event(K_RWIN, false, kb->time);
			return 1;
		}

		break;
	}

	return CallNextHookEx(sys.hHookKeyboard, nCode, wParam, lParam);
}

/*
 ==================
 WIN_EnableWinKeys
 ==================
*/
static void WIN_EnableWinKeys (bool enable){

	if (enable){
		if (sys.hHookKeyboard)
			return;

		sys.hHookKeyboard = SetWindowsHookEx(WH_KEYBOARD_LL, WIN_LowLevelKeyboardProc, sys.hInstance, 0);
	}
	else {
		if (!sys.hHookKeyboard)
			return;

		UnhookWindowsHookEx(sys.hHookKeyboard);
		sys.hHookKeyboard = NULL;
	}
}

/*
 ==================
 WIN_AppActivate
 ==================
*/
static void WIN_AppActivate (bool active){

	Com_DPrintf("WIN_AppActivate( %i )\n", active);

	Key_ClearStates();
	IN_ClearStates();

	WIN_EnableWinKeys(active);

	// Restore or minimize on demand
	if (active){
		if (sys.hWndMain && sys.isFullscreen){
			ShowWindow(sys.hWndMain, SW_RESTORE);
			SetForegroundWindow(sys.hWndMain);
			SetFocus(sys.hWndMain);
		}

		S_Activate(true);
		IN_Activate(true);
	}
	else {
		IN_Activate(false);
		S_Activate(false);

		if (sys.hWndMain && sys.isFullscreen)
			ShowWindow(sys.hWndMain, SW_MINIMIZE);
	}

	sys.active = active;
}

/*
 ==================
 WIN_MouseState
 ==================
*/
static int WIN_MouseState (int state){

	int		buttonState = 0;

	if (state & MK_LBUTTON)
		buttonState |= BIT(0);
	if (state & MK_RBUTTON)
		buttonState |= BIT(1);
	if (state & MK_MBUTTON)
		buttonState |= BIT(2);
	if (state & MK_XBUTTON1)
		buttonState |= BIT(3);
	if (state & MK_XBUTTON2)
		buttonState |= BIT(4);

	return buttonState;
}

/*	
 ==================
 WIN_MapKey
 ==================
*/
static int WIN_MapKey (int keyCode){

	int		scanCode;
	int		key;

	scanCode = (keyCode >> 16) & 255;
	if (scanCode > 127)
		return 0;

	key = win_scanCodeToKey[scanCode];

	if (keyCode & BIT(24)){
		switch (key){
		case K_LALT:
			return K_RALT;
		case K_LCTRL:
			return K_RCTRL;
		case K_ENTER:
			return K_KP_ENTER;
		case K_PAUSE:
			return K_KP_NUMLOCK;
		case '/':
			return K_KP_SLASH;
		}
	}
	else {
		switch (key){
		case K_UPARROW:
			return K_KP_UPARROW;
		case K_DOWNARROW:
			return K_KP_DOWNARROW;
		case K_LEFTARROW:
			return K_KP_LEFTARROW;
		case K_RIGHTARROW:
			return K_KP_RIGHTARROW;
		case K_INS:
			return K_KP_INS;
		case K_DEL:
			return K_KP_DEL;
		case K_HOME:
			return K_KP_HOME;
		case K_END:
			return K_KP_END;
		case K_PGUP:
			return K_KP_PGUP;
		case K_PGDN:
			return K_KP_PGDN;
		}
	}

	return key;
}

/*
 ==================
 WIN_MainWindowProc

 Main window procedure
 ==================
*/
LRESULT CALLBACK WIN_MainWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	switch (uMsg){
	case WM_CREATE:
		sys.hWndMain = hWnd;

		break;
	case WM_DESTROY:
		sys.hWndMain = NULL;

		break;
	case WM_ACTIVATE:
		// We don't want to act like we're active if we're minimized
		if ((LOWORD(wParam) != WA_INACTIVE) && !(HIWORD(wParam)))
			WIN_AppActivate(true);
		else
			WIN_AppActivate(false);
		
		break;
	case WM_CLOSE:
		PostQuitMessage(0);
		
		break;
	case WM_MOVE:
		if (!sys.isFullscreen){
			RECT	rect;
			int		style, exStyle;
			int		x, y;

			x = (int)((short)LOWORD(lParam));	// Horizontal position 
			y = (int)((short)HIWORD(lParam));	// Vertical position 

			rect.left = 0;
			rect.top = 0;
			rect.right = 1;
			rect.bottom = 1;

			style = GetWindowLong(hWnd, GWL_STYLE);
			exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);

			AdjustWindowRectEx(&rect, style, FALSE, exStyle);

			CVar_SetVariableInteger("win_xPos", x + rect.left);
			CVar_SetVariableInteger("win_yPos", y + rect.top);

			if (sys.active)
				IN_Activate(false);
		}

		break;
	case WM_CAPTURECHANGED:
		if (sys.active)
			IN_Activate(false);

		break;
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_RBUTTONDOWN:
	case WM_RBUTTONUP:
	case WM_MBUTTONDOWN:
	case WM_MBUTTONUP:
	case WM_XBUTTONDOWN:
	case WM_XBUTTONUP:
	case WM_MOUSEMOVE:
		IN_MouseButtonEvent(WIN_MouseState(wParam));

		break;
	case WM_MOUSEWHEEL:
		{
			int		state, delta;

			state = (int)((short)LOWORD(wParam));
			delta = (int)((short)HIWORD(wParam));

			IN_MouseButtonEvent(WIN_MouseState(state));

			IN_MouseWheelEvent(delta / WHEEL_DELTA);
		}

		break;
	case WM_SYSCOMMAND:
		if (wParam == SC_KEYMENU || wParam == SC_SCREENSAVE || wParam == SC_MONITORPOWER)
			return 0;

		break;
	case WM_SYSKEYDOWN:
		if (wParam == VK_RETURN){
			CVar_SetVariableInteger("r_fullscreen", !sys.isFullscreen);
			Cmd_AppendText("restartVideo\n");

			return 0;
		}

		// Fall through

	case WM_KEYDOWN:
		Key_Event(WIN_MapKey(lParam), true, sys.msgTime);

		break;
	case WM_SYSKEYUP:
	case WM_KEYUP:
		Key_Event(WIN_MapKey(lParam), false, sys.msgTime);

		break;
	case WM_CHAR:
		if (((lParam >> 16) & 255) == 41)
			break;		// Ignore the console key

		Key_CharEvent(wParam);

		break;
	}
	
	// Pass all unhandled messages to DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*
 ==================
 WIN_FakeWindowProc

 Fake window procedure
 ==================
*/
LRESULT CALLBACK WIN_FakeWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	switch (uMsg){
	case WM_CREATE:
		sys.hWndFake = hWnd;

		break;
	case WM_DESTROY:
		sys.hWndFake = NULL;

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
